/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Windows.Threading;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data.Utils
{
  internal abstract class SLWeakEventManager
  {
    protected SLWeakEventManager()
    {
    }

    protected static void SetCurrentManager( Type managerType, SLWeakEventManager manager )
    {
      if( managerType == null )
        throw new ArgumentNullException( "managerType" );

      if( manager == null )
        throw new ArgumentNullException( "manager" );

      SLWeakEventManager.Managers[ managerType ] = manager;
    }

    protected void DeliverEvent( object sender, EventArgs args )
    {
      if( sender == null )
        throw new ArgumentNullException( "sender" );

      if( args == null )
        throw new ArgumentNullException( "args" );

      SLWeakEventManager registeredManager = SLWeakEventManager.GetCurrentManager( this.GetType() );

      if( registeredManager != this )
        throw new InvalidOperationException( "An attempt was made to deliver an event using a manager other than the current one." );

      lock( this )
      {
        List<WeakReference> listeners = null;

        foreach( WeakReference sourceReference in m_dictionary.Keys )
        {
          if( sourceReference.IsAlive && ( sourceReference.Target != null ) && ( sourceReference.Target == sender ) )
          {
            listeners = m_dictionary[ sourceReference ];
            break;
          }
        }

        if( listeners != null )
        {
          Type managerType = this.GetType();

          foreach( WeakReference listenerWeakRef in listeners.ToArray() )
          {
            if( listenerWeakRef.IsAlive && ( listenerWeakRef.Target != null ) )
            {
              ISLWeakEventListener listener = ( ISLWeakEventListener )listenerWeakRef.Target;
              listener.ReceiveWeakEvent( managerType, sender, args );
            }
            else
            {
              SLWeakEventManager.ScheduleCleanup();
            }
          }
        }
        else
        {
          SLWeakEventManager.ScheduleCleanup();
        }
      }
    }

    protected void AddListenerProtected( object source, ISLWeakEventListener listener )
    {
      if( source == null )
        throw new ArgumentNullException( "source" );

      if( listener == null )
        throw new ArgumentNullException( "listener" );

      SLWeakEventManager registeredManager = SLWeakEventManager.GetCurrentManager( this.GetType() );

      if( registeredManager != this )
        throw new InvalidOperationException( "An attempt was made to add a listener using a manager other than the current one." );

      lock( this )
      {
        WeakReference weakRef = new WeakReference( listener );

        bool newSource = false;

        List<WeakReference> listeners = null;

        foreach( WeakReference sourceReference in m_dictionary.Keys )
        {
          if( sourceReference.IsAlive && ( sourceReference.Target != null ) && ( sourceReference.Target == source ) )
          {
            listeners = m_dictionary[ sourceReference ];
            break;
          }
        }

        if( listeners == null )
        {
          listeners = new List<WeakReference>();
          m_dictionary.Add( new WeakReference( source ), listeners );
          listeners.Add( weakRef );
          newSource = true;
        }
        else
        {
          bool needAdd = true;
          foreach( WeakReference existingWeakRefs in listeners )
          {
            if( existingWeakRefs.IsAlive && ( existingWeakRefs.Target != null ) && ( existingWeakRefs.Target == listener ) )
            {
              needAdd = false;
              break;
            }
          }

          if( needAdd )
          {
            listeners.Add( weakRef );
          }
        }

        if( newSource )
        {
          this.StartListening( source );
        }

        SLWeakEventManager.ScheduleCleanup();
      }
    }

    protected void RemoveListenerProtected( object source, ISLWeakEventListener listener )
    {
      if( source == null )
        throw new ArgumentNullException( "source" );

      if( listener == null )
        throw new ArgumentNullException( "listener" );

      SLWeakEventManager registeredManager = SLWeakEventManager.GetCurrentManager( this.GetType() );

      if( registeredManager != this )
        throw new InvalidOperationException( "An attempt was made to remove a listener using a manager other than the current one." );


      lock( this )
      {
        bool unregister = false;

        List<WeakReference> listeners = null;
        WeakReference sourceWeakRef = null;

        foreach( WeakReference sourceReference in m_dictionary.Keys )
        {
          if( sourceReference.IsAlive && ( sourceReference.Target != null ) && ( sourceReference.Target == source ) )
          {
            listeners = m_dictionary[ sourceReference ];
            sourceWeakRef = sourceReference;
            break;
          }
        }

        if( listeners != null )
        {
          WeakReference removed = null;
          foreach( WeakReference weakRef in listeners )
          {
            if( weakRef.IsAlive && ( weakRef.Target != null ) && ( weakRef.Target == listener ) )
            {
              removed = weakRef;
              break;
            }
          }

          if( removed != null )
          {
            listeners.Remove( removed );
          }

          if( listeners.Count == 0 )
          {
            m_dictionary.Remove( sourceWeakRef );
            unregister = true;
          }
        }

        if( unregister )
        {
          this.StopListening( source );
        }

        SLWeakEventManager.ScheduleCleanup();
      }
    }

    protected abstract void StartListening( object source );

    protected abstract void StopListening( object source );

    private static void ScheduleCleanup()
    {
      lock( SLWeakEventManager.Managers )
      {
        if( CleanupDispatcherOperation == null )
        {
          try
          {
            Dispatcher dispatcher;

#if SILVERLIGHT
            dispatcher = Deployment.Current.Dispatcher;
#else
            dispatcher = Dispatcher.CurrentDispatcher;
#endif

            CleanupDispatcherOperation = dispatcher.BeginInvoke( new Action( SLWeakEventManager.Cleanup ) );
          }
          catch
          {
          }
        }
      }
    }

    private static void Cleanup()
    {
      lock( SLWeakEventManager.Managers )
      {
        List<WeakReference> toRemove = new List<WeakReference>();

        foreach( SLWeakEventManager manager in SLWeakEventManager.Managers.Values )
        {
          toRemove.Clear();

          foreach( KeyValuePair<WeakReference, List<WeakReference>> keyValuePair in manager.m_dictionary )
          {
            bool removeSource = false;

            if( !keyValuePair.Key.IsAlive )
            {
              removeSource = true;
            }

            int listenerCount = keyValuePair.Value.Count - 1;

            while( listenerCount >= 0 )
            {
              if( !keyValuePair.Value[ listenerCount ].IsAlive )
              {
                keyValuePair.Value.RemoveAt( listenerCount );
              }

              listenerCount--;
            }

            if( keyValuePair.Value.Count == 0 )
            {
              removeSource = true;
            }

            if( removeSource )
            {
              keyValuePair.Value.Clear();
              toRemove.Add( keyValuePair.Key );
            }
          }

          foreach( WeakReference removedSource in toRemove )
          {
            manager.m_dictionary.Remove( removedSource );
          }
        }

        SLWeakEventManager.CleanupDispatcherOperation = null;
      }
    }

    private static SLWeakEventManager GetCurrentManager( Type managerType )
    {
      SLWeakEventManager manager = null;
      if( !SLWeakEventManager.Managers.TryGetValue( managerType, out manager ) )
        return null;

      return manager;
    }

    private static Dictionary<Type, SLWeakEventManager> Managers = new Dictionary<Type, SLWeakEventManager>();
    private static DispatcherOperation CleanupDispatcherOperation;//null

    private Dictionary<WeakReference, List<WeakReference>> m_dictionary = new Dictionary<WeakReference, List<WeakReference>>();
  }
}
